/**
 * 
 * This file contains the Main class for the application. This is class that
 * actually launches the application and initialises the visual elements.  
 * 
 * Author: Matthias Van Eeghem <matthias@van-eeghem.com>
 * All rights reserved.
 * 
 */

package com.bomberman.core;

import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.*;

import com.bomberman.Main;
import com.bomberman.bomb.Bomb;
import com.bomberman.bomb.BombManager;
import com.bomberman.brick.Brick;
import com.bomberman.brick.BrickManager;
import com.bomberman.core.Element;
import com.bomberman.core.GameState;
import com.bomberman.core.GameState.eState;
import com.bomberman.explosion.Explosion;
import com.bomberman.explosion.ExplosionManager;
import com.bomberman.player.Player;
import com.bomberman.player.PlayerManager;

@SuppressWarnings("serial")
public class Game extends JPanel 
{
	/**
	 * The amount of rows in the application
	 */
	public static int AMOUNT_OF_ROWS = 16;
	/**
	 * The amount of columns in the application
	 */
	public static int AMOUNT_OF_COLUMNS = 20;
	/**
	 * Width of a brick image in pixels
	 */
	public static int BRICK_WIDTH = 32;
	/**
	 * The minimum x coordinate of the first brick (in the upper left corner of the field)
	 * The minimum x coordinate is the right-most coordinate on the x-axis.
	 */
	public static int FIRSTBRICK_MIN_X_COORDINATE = 74;
	/**
	 * The maximum y coordinate of the first brick (in the upper left corner of the field)
	 * The maximum y coordinate is the highest y coordinate of the brick, but is actually the lowest
	 * in value. (The y axis is flipped)
	 */
	public static int FIRSTBRICK_MAX_Y_COORDINATE = 48;
	/**
	 * The minimum x coordinate of the last brick (in the down right corner of the field)
	 * The minimum x coordinate is the right-most coordinate on the x-axis.
	 */
	public static int LASTBRICK_MIN_X_COORDINATE = 681;
	/**
	 * The maximum y coordinate of the last brick (in the down right corner of the field)
	 * The maximum y coordinate is the highest y coordinate of the brick, but is actually the lowest
	 * in value. (The y axis is flipped)
	 */
	public static int LASTBRICK_MAX_Y_COORDINATE = 527;
	/**
	 * The ID for an invalid row (a row which isn't on the field)
	 */
	public static int INVALID_ROW_ID = -1;
	/**
	 * The ID for an invalid column (a column which isn't on the field)
	 */
	public static int INVALID_COLUMN_ID = -1;
	/**
	 * Default speed for the player (speed represents the amount of pixels the player is moved in any direction)
	 */
	public static int PLAYER_DEFAULT_SPEED = 32;
	/**
	 * The visual part of the application is redrawn every REPAINT_TIME milliseconds
	 */
	public static int REPAINT_TIME = 10;
	/**
	 * Every BOMB_TICK_TIME milliseconds a new bomb frame should be drawn
	 */
	public static int BOMB_TICK_TIME = 150;
	/**
	 * Every HEARTBEART_TIME milliseconds the host sends a heartbeat to the masterlist
	 */
	public static int HEARTBEAT_TIME = 5000;
	/**
	 * Game over for this player?
	 */
	public static boolean gameOver = false;
	
	// Non-static defines
	/**
	 * The image for the brick object (32x32)
	 */
	private Image brickImage = null;
	/**
	 * The image for the player object (32x32)
	 */
	private Image playerImage = null;
	/**
	 * The background image ingame (fills up the entire app) (800x600)
	 */
	private Image gameBackgroundImage = null;
	/**
	 * The bomb images (32x32)
	 */
	private ArrayList<Image> bombImages = new ArrayList<Image>();
	/**
	 * The explosion images (32x32)
	 */
	private Image explosionImage = null;
	/**
	 * Holds the font to draw the strings with
	 */
	private Font calibriFont = null;
	/**
	 * Holds the main object (menu etc)
	 */
	private Main main = null;
	/**
	 * Hold the local player
	 */
	private Player localPlayer = null;
	
	/**
	 * This method initialises the actual game. It loads the images into the memory, generates a field with bricks randomly placed.
	 * It also creates the local player. It also consists of a timer which re-paints the visual part of the application every 10 ms.
	 */
	public Game(final Main main, final boolean isHost)
	{
		this.main = main;

		calibriFont = new Font("Arial", Font.BOLD, 12);
		
		// Load all images into the memory
		brickImage = this.getToolkit().getImage(getClass().getResource("/com/bomberman/resources/brick.jpg"));
		playerImage = this.getToolkit().getImage(getClass().getResource("/com/bomberman/resources/player.png"));
		gameBackgroundImage = this.getToolkit().getImage(getClass().getResource("/com/bomberman/resources/game-background.jpg"));
		explosionImage = this.getToolkit().getImage(getClass().getResource("/com/bomberman/resources/explosion.png"));
		
		for(int i = 1; i < 15; i++)
		{
			bombImages.add(this.getToolkit().getImage(getClass().getResource("/com/bomberman/resources/bomb/bomb" + i + ".png")));
		}
		
		new Thread(new Runnable() 
		{
			public void run() 
			{
				// Start the rpeaint timer 
				new Timer(REPAINT_TIME, new ActionListener() { public void actionPerformed(ActionEvent e) { repaint(); } }).start();
				// Bomb tick timer
				new Timer(BOMB_TICK_TIME, new ActionListener() { public void actionPerformed(ActionEvent e) { NewFrameTimer(); } }).start();
				// Heartbeat timer, sends a "heartbeat" to the masterlist
				new Timer(HEARTBEAT_TIME, new ActionListener() { public void actionPerformed(ActionEvent e) { SendHeartbeat(); } }).start();
			}
		}).start();
		
		// Set the game state
		GameState.setGameState(eState.GameStateConnected);
	}
	
	/**
	 * Get the local player
	 * 
	 * @return
	 */
	public Player getLocalPlayer()
	{
		// Was this method called before? If not, we search the local player
		if(localPlayer == null)
		{
			// Loop through all players
			for(Player player : PlayerManager.getPlayerList())
			{
				// Is the name a match with our local player's name?
				if(player.getName().equals(Main.playerName))
					// Set the player object
					localPlayer = player;
			}
		}
		
		// It was already called, just return the player
		return localPlayer;
	}
	
	/**
	 * Every HEARTBEART_TIME milliseconds the host sends a heartbeat to the masterlist
	 */
	private void SendHeartbeat()
	{
		try
		{
			new URL("http://matthias.van-eeghem.com/dev/JavaBomb/Heartbeat.php?version=someVersion&port=6161&players=2&hostname=YOLO&security_key=yUka8Uc9").openStream().close();
		}
		catch (Exception e)
		{
			System.out.println("Failed to send data!");
		}		
	}
	
	/**
	 * Called each 150ms, each time this is called a new image should be painted
	 */
	public void NewFrameTimer()
	{
		Iterator<Bomb> bombIterator = BombManager.getBombList().iterator();

		// Loop through all bombs
		while(bombIterator.hasNext())
		{
			Bomb bomb = bombIterator.next();
			
			// As long as 2 seconds haven't passed, increment the state (which also changes the image to draw in paint())
			if(bomb.getState() != Bomb.BombState.HAS_TO_EXPLODE)
			{
				bomb.incrementState();
			}
			// Bomb has to explode
			else
			{
				// Get the bomb's row & column
				int row = bomb.getRow();
				int column = bomb.getColumn();
				
				// Remove the bricks around the bombs
				BrickManager.removeBrickAt(row + 1, column);
				BrickManager.removeBrickAt(row - 1, column);
				BrickManager.removeBrickAt(row, column + 1);
				BrickManager.removeBrickAt(row, column - 1);
				
				// Add explosions in place of the newly removed blocks
				// We check if the explosions are "within boundaries" before adding them
				ExplosionManager.addExplosion(new Explosion(bomb.getBombOwner(), row, column));
				if((row + 1) != AMOUNT_OF_ROWS)
					ExplosionManager.addExplosion(new Explosion(bomb.getBombOwner(), row + 1, column));
				if((row - 1) != INVALID_ROW_ID)
					ExplosionManager.addExplosion(new Explosion(bomb.getBombOwner(), row - 1, column));
				if((column + 1) != AMOUNT_OF_COLUMNS)
					ExplosionManager.addExplosion(new Explosion(bomb.getBombOwner(), row, column + 1));
				if((column - 1) != INVALID_COLUMN_ID)
					ExplosionManager.addExplosion(new Explosion(bomb.getBombOwner(), row, column - 1));
				
				// Check if there are any players in the bomb's range, if so, kill them
				bomb.killPlayersInRange();
				
				// Remove the bomb from the elements list
				Element.remove(bomb);
				// Remove the bomb from Field.bombList
				bombIterator.remove();
			}
		}
		
		Iterator<Explosion> explosionIterator = ExplosionManager.getExplosionList().iterator();

		// Loop through all bombs
		while(explosionIterator.hasNext())
		{
			Explosion explosion = explosionIterator.next();
			
			// As long as 1 second hasn't passed, increment the state (which also changes the image to draw in paint())
			if(explosion.getState() != Explosion.ExplosionState.HAS_TO_BE_REMOVED)
			{
				explosion.incrementState();
			}
			
			// Explosion has to fade away
			else
			{
				// Remove the explosion from the elements list
				Element.remove(explosion);
				// Remove the explosion from Field.explosionList
				explosionIterator.remove();
			}
		}
	}

	/**
	 * The key event is an event which is triggered by a key press from the users. It's being called from the Main() method.
	 */
	public void keyEvent(KeyEvent keyEvent)
	{
		if(keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
		{
			main.setVisible(true);
			main.getGameFrame().setVisible(false);
		}
		
		// The up button
		if(keyEvent.getKeyCode() == KeyEvent.VK_UP)
		{
			getLocalPlayer().moveUp();
		}
		
		// The down button
		if(keyEvent.getKeyCode() == KeyEvent.VK_DOWN)
		{
			getLocalPlayer().moveDown();
		}
		
		//  The left button
		if(keyEvent.getKeyCode() == KeyEvent.VK_LEFT)
		{
			getLocalPlayer().moveToLeft();
		}
		
		// The right button
		if(keyEvent.getKeyCode() == KeyEvent.VK_RIGHT)
		{
			getLocalPlayer().moveToRight();
		}
		
		if(keyEvent.getKeyCode() == KeyEvent.VK_SPACE)
		{
			Player player = getLocalPlayer();
			BombManager.add(new Bomb(player, player.getRows().get(0), player.getColumns().get(0)));
		}
	}

	/**
	 * The paint method is triggered by the application itself, and re-paints the visual part of the application.
	 * In order to repaint the application, paint should not be triggered, instead use repaint().
	 * This method renders the bricks, the background and the player.
	 * 
	 * (non-Javadoc)
	 * @see javax.swing.JComponent#paint(java.awt.Graphics)
	 */
	public void paint(Graphics graphics)
	{
		if(!gameOver)
		{
			// Set the font
			graphics.setFont(calibriFont);
			
			// Draw the game's backgroud
			graphics.drawImage(gameBackgroundImage, 0, 0, null);
			
			// Loop through the brick list
			for (Brick brick : BrickManager.getBrickList())
			{
				// Draw the brick image
				graphics.drawImage(brickImage, brick.getMinimumXCoordinate(), brick.getMaximumYCoordinate(), null);
			}
			
			// Draw all bombs
			for (Bomb bomb : BombManager.getBombList())
			{
				Image image = bombImages.get(bomb.getState().ordinal());
					
				// Draw the player image
				graphics.drawImage(image, bomb.getMinimumXCoordinate(), bomb.getMaximumYCoordinate(), null);
			}
			
			// Draw all explosions
			for (Explosion explosion : ExplosionManager.getExplosionList())
			{
				//Image image = bombImages.get(explosion.getState().ordinal());
					
				// Draw the player image
				graphics.drawImage(explosionImage, explosion.getMinimumXCoordinate(), explosion.getMaximumYCoordinate(), null);
			}
			
			// Draw all players
			for (Player player : PlayerManager.getPlayerList())
			{
				// Get the fonnt metrics
				FontMetrics fontMetrics = graphics.getFontMetrics();
				// Get the width of the string
				int width = fontMetrics.stringWidth(player.getName());

				// Draw the player image
				graphics.drawImage(playerImage, player.getMinimumXCoordinate(), player.getMaximumYCoordinate(), null);
				graphics.drawString(player.getName() + " (" + player.getID() + ")", player.getMinimumXCoordinate() - (width / 2) + 15, player.getMaximumYCoordinate() - 5);
			}
			
			graphics.drawString(GameState.getStateAsString(), 6, 592);
		}
		else
		{
			//@TODO: Handle game over
			graphics.drawImage(gameBackgroundImage, 0, 0, null);
		}
	}
}
           